//also see SerialTwoWayEventComm.java where the code was annotated first and more carefully than this one
//code is from: http://rxtx.qbang.org/wiki/index.php/Two_way_communcation_with_the_serial_port
//make sure you have something plugged into COM3 per the code
//make sure you set up the Native library location to this project folder as well as import the library
//always update the Native library location if you rename the project folder

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

public class TwoWayThreadSerialComm
{
    public TwoWayThreadSerialComm()
    {
        super();
    }
    
    void connect ( String portName ) throws Exception//takes in the COM port number
    {
        CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);//passes the COM port number and creates a CommPortIdentifier which contains generic information about the COM port.
		//note at this stage it can be a serial or parallel port
        if ( portIdentifier.isCurrentlyOwned() )//check to see if anyone is already using this COM port
        {
            System.out.println("Error: Port is currently in use");
        }
        else
        {
            CommPort commPort = portIdentifier.open(this.getClass().getName(),2000);
			//getClass comes from the class java.lang.Object which is inherited. It returns the runtime class (not the class name in a string but the actual class) hence the return type is Class<?> which is a Class object of wildcard type
			//Refer to: http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html
			//Also refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPortIdentifier.html#open(java.lang.String, int)
			
			//Opens the communications port. Passes the name of the COM port as well as set the timeout to 2000
			//Refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPortIdentifier.html
			//when the comm port is open, it returns a CommPort object. "There are no public constructors for CommPort. Instead an application should use the static method CommPortIdentifier.getPortIdentifiers to generate a list of available ports."
			//Refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPort.html
            
            if ( commPort instanceof SerialPort )//if the commPort type is a Serial Port
            {
                SerialPort serialPort = (SerialPort) commPort;//cast the commPort reference to a SerialPort reference
                serialPort.setSerialPortParams(57600,SerialPort.DATABITS_8,SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);//set the baud, data bits, stop bits, and parity
                
                InputStream in = serialPort.getInputStream();//poll the serial port's input steam and receive any serial message
				//getInputStream() is inherited from CommPort which is the parent class of SerialPort
                OutputStream out = serialPort.getOutputStream();//poll the serial port's output steam and send any serial message
                //OutputStream() is inherited from CommPort which is the parent class of SerialPort
                (new Thread(new SerialReader(in))).start();//start a new thread to read the serial data from the serial input stream, and print it out to the system console
                (new Thread(new SerialWriter(out))).start();//start a new thread to write the serial data to the serial input stream, from the system console user keyboard input

            }
            else//print the following message about the COM port since it's not a serial port
            {
                System.out.println("Error: Only serial ports are handled by this example.");
            }
        }     
    }
    
    /** */
    public static class SerialReader implements Runnable //implement runnable so a thread can be created
    {
        InputStream in;
        
        public SerialReader ( InputStream in )
        {
            this.in = in;//takes in the input stream passed to the constructor and set it to the instance's instance variable
        }
        
        public void run ()//create an overriding run method for the thread to use
        {
            byte[] buffer = new byte[1024];//buffer to store the serial message coming in
            int len = -1;//this holds the length of the buffer, set it to some byte value such as -1 which means nothing
            try
            {
                while ( ( len = this.in.read(buffer)) > -1 )//Calls the overloaded public int read(byte[] b)
				//Reads some number of bytes from the input stream's reference "in" and stores them into the buffer array
				//len gets the total number of bytes read into the buffer, or -1 if there is no more data because the end of the stream has been reached.
				//refer to http://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html#read()
				//Note: the next byte of data, or -1 if the end of the stream is reached.
                {
                    System.out.print(new String(buffer,0,len));//creates a new string with 0 offset and with length of the buffer and pring it out to the console
				//Using this overloaded constructor for string: String(byte[] bytes, int offset, int length)
				//http://docs.oracle.com/javase/6/docs/api/java/lang/String.html
                }                                
            }
            catch ( IOException e )
            {
                e.printStackTrace();
            }            
        }
    }

    /** */
    public static class SerialWriter implements Runnable //implement runnable so a thread can be created
    {
        OutputStream out;
        
        public SerialWriter ( OutputStream out )
        {
            this.out = out;//takes in the output stream passed to the constructor and set it to the instance's instance variable
        }
        
        public void run ()//create an overriding run method for the thread to use
        {
            try
            {                
                int c = 0;//will store the single byte character read in form the user input keyboard (one at a time)
                while ( ( c = System.in.read()) > -1 )//read in the user input keyboard one byte at a time
                {
                    this.out.write(c);//write the user input byte to the output stream which is then passed to where the SerialWriter was constructed above which gets the output stream from the serialPort object
                }                
            }
            catch ( IOException e )
            {
                e.printStackTrace();
            }            
        }
    }
    
    
    //Added this code in myself from another example
    
    static void listPorts()
    {
        java.util.Enumeration<CommPortIdentifier> portEnum = CommPortIdentifier.getPortIdentifiers();
		//getPortIdentifiers() Obtains an enumeration object that contains a CommPortIdentifier object for each port in the system.
		//Create an object reference of the Enumeration interface type (containing/storing CommPortIdentifier type object references) and call it portEnum
		//Refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPortIdentifier.html#getPortIdentifiers()
		//Also Refer to: http://docs.oracle.com/javase/6/docs/api/java/util/Enumeration.html
		
		
        while ( portEnum.hasMoreElements() ) //Tests if portEnum enumeration contains more elements.
        {
            CommPortIdentifier portIdentifier = portEnum.nextElement();//Returns the next element (of CommPortIdentifier type objects) of portEnum enumeration if this enumeration object has at least one more element to provide and is pointed to be the object reference portIdentifier of CommPortIdentifier class type
            System.out.println(portIdentifier.getName()  +  " - " +  getPortTypeName(portIdentifier.getPortType()) );
			//Note: getPortTypeName() is defined below in this code
			//Get and print the portIdentifier's name and the port type
			//getName() = Returns the name of the port
			//getPortTypeName() =  returns the type of the port by converting the integer to a String corresponding to the port type
			//getPortType() returns an integer that corresponds to the port type
			//Refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPortIdentifier.html#getPortIdentifiers()
			//Also refer to: http://docs.oracle.com/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPortIdentifier.html#getPortType()
			
        }        
    }
    
    static String getPortTypeName ( int portType )//converts the integer value representation of the portType to a String so it can be later on printed
    {
        switch ( portType )
        {
            case CommPortIdentifier.PORT_I2C:
                return "I2C";
            case CommPortIdentifier.PORT_PARALLEL:
                return "Parallel";
            case CommPortIdentifier.PORT_RAW:
                return "Raw";
            case CommPortIdentifier.PORT_RS485:
                return "RS485";
            case CommPortIdentifier.PORT_SERIAL:
                return "Serial";
            default:
                return "unknown type";
        }
    }
    
    
    
    public static void main ( String[] args )
    {
    	    	
        try
        {
        	
        	String comPort;//create a string object to store the serial comm port number that the user enters in
        	BufferedReader keybd =
        			 new BufferedReader(new InputStreamReader(System.in));//create a buffered reader object to read in keyboard user input
        	TwoWayThreadSerialComm.listPorts();//calling method to see what ports are available, then print it out to the system console
			//Note: Calling a static method, so no instance. Just call the ClassName.methodName();
        	//(new TwoWayThreadSerialComm()).connect("COM3");//comment out original code in the example, leave it here for debugging. It is for hardcoding the com port number instead of user input
        	System.out.println("Please enter the COM port you wish to use (i.e. COM3): ");//Prompt user to enter a COM port
        	comPort = keybd.readLine();//read the com port from the user input (hopefully in the correct format of COM# where # is the number of the COM port)
        	(new TwoWayThreadSerialComm()).connect(comPort);//Passing the user input to the TwoWayThreadSerialComm's connect method
        }
        catch ( Exception e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}